/**
 * 
 */
package cn.biplam.common.file;

import java.util.ArrayList;

import cn.biplam.common.wrapper.ErrorMessage;

/**
 * CSV格式文本文件读取(以回车+换行为行尾,逗号分隔字符段的文本文件)<BR>
 * 逐行读取,识别双引号内的换行符，并脱去双引号<BR>
 * 使用方法：<UL>
 * <LI>读取文件前,必须调用prepareRead()准备数据环境
 * <LI><Font color=red>读取文件后，必须调用close()关闭缓冲区</font>
 * <LI>read(char[])字节读,readln()读取一行,返回字符串
 * <LI>readLine()读一行，返回ArrayList.
 * </UL>
 * 内部计数器说明：<UL>
 * <LI>行数：1-n. 
 * <LI>列数：1-n. 
 * </UL>
 * 
 * @author 	WangSong 2009-6-21 下午10:36:45
 * @version 1.0
 */
public class CsvFile extends TxtFile{

	/** 当前列数*/
	private int cols;
	/**当前行数*/
	private int rows=0;
	/**文件结束标志*/
	private boolean eof=false;
	/**读文件指针*/
	private char[] buf=null;

	public CsvFile(String sPath,String sFileName){
		super(sPath,sFileName);
	}

	/**
	 * 读取一行(识别双引号内的换行符，并脱去双引号)
	 * @return
	 * @throws ErrorMessage
	 */
	@Override
	public String readln() throws ErrorMessage {
		StringBuilder sb = new StringBuilder();
		for(String ite:readLine()){
			sb.append(ite);
			sb.append(',');
		}
		if(sb.length()>0)
			sb.delete(sb.length()-1, sb.length());
		return sb.toString();
	}

	/**
	 * 
	 * @throws ErrorMessage
	 */
	@Override
	public void prepareRead() throws ErrorMessage {
		this.buf=new char[1];
		super.prepareRead();
	}

	/**
	 * 读取一行
	 * @return	列数据集合
	 * @throws ErrorMessage
	 */
	public ArrayList<String> readLine() throws ErrorMessage{
		ArrayList<String> fields = new ArrayList<String>();
		this.cols=0;

		//1、在循环开始前，读取第一个字符，进行2步处理
		if(this.readNext(buf)<0){//如果是空文件，置文件结束标志，返回空数组
			return fields;
		}

		if(buf[0]=='\r'){//如果是空行，处理行尾，返回空数组
			this.lfProcess(buf);
			return fields;
		}


		//2、接着判断如果第一个字符是逗号、引号或其它字符，进行3步处理(调读字段函数)
		this.cols++;
		fields.add(this.readField(buf));

		//3、循环前判断第一个字段后的指针：
		//	1、如果指针是逗号，表示有下一个字段
		//	2、如果指针是换行符，表示该行只有一个字段，处理行尾并返回
		//	3、如果是文件尾，表示缺少换行符，抛出异常
		if(this.eof){
			throw new ErrorMessage("缺少换行结束符.buf="+(int)buf[0]+" at line "+String.valueOf(rows+1));
		}
		if(buf[0]=='\r'){
			this.lfProcess(buf);
			return fields;
		}
		if(buf[0]!=','){
			throw new ErrorMessage("未知的字段结束符(期望逗号,实际="+(int)buf[0]+" at line "+String.valueOf(rows+1)+",column "+String.valueOf(this.cols)+".");
		}

		//是逗号，开始循环
		while(buf[0]==','){
			this.cols++;
			//	1、读取下一个字符，判断指针
			//		1、如果指针是逗号，表示空字段，保存空字段，指针已经在结束符上
			//		2、如果指针是回车，保存空字段，处理行尾，指针已经在结束符上
			//		3、如果指针是文件尾，表示缺少换行符，抛出异常
			//		4、剩下的只能是文本或数字字符段了，进行字段读取处理，指针将移动到结束符
			if(this.readNext(buf)<0){//文件尾
				throw new ErrorMessage("缺少换行结束符."+" at line "+String.valueOf(rows+1));
			}

			if(buf[0]=='\r'){//回车,并退出
				fields.add("");
				this.lfProcess(buf);
				break;
			}
			//逗号和其它情况在readField方法中处理
			fields.add(this.readField(buf));

			//	2、读取完一个字段后，指针将移动到该字段后面的结束符上，接着判断结束符
			//		1、如果是逗号，表示还有下一个字段，继续循环
			//		2、如果是回车，表示该字段是最后一个字段，且有内容，是由字段读取函数移动来的，处理行尾并返回
			//		3、如果是文件尾，表示该字段是最后一个字段，且有内容，是由字段读取函数移动来的，但仍然意味着缺少换行符，抛出异常
			if(buf[0]=='\r'){//行尾
				this.lfProcess(buf);
			}else if(this.eof){//文件尾
				throw new ErrorMessage("缺少换行结束符."+" at line "+String.valueOf(rows+1)+",column "+String.valueOf(this.cols)+".");
			}
		}

		return fields;
	}

	/**
	 * 行尾处理. 移动buf指针略过多余的行尾符号，将指针指向\r后的\n
	 */
	private void lfProcess(char[] buf) throws ErrorMessage{
		this.readNext(buf);
		if(buf[0]!='\n'){//0A ascii=10 键盘=Shift+Enter
			throw new ErrorMessage("未知的结束符(期望\\n,实际="+(int)buf[0]+" at line "+String.valueOf(rows+1)+",column "+String.valueOf(this.cols)+".");
		}
		this.rows++;
	}

	/**
	 * 读取一个字符段(逗号分隔)
	 */
	private String readField(char[] buf) throws ErrorMessage{
		/**分隔符检查.处理当前字段为空的情况*/
		if(buf[0]==','){//该字符段内容为空,添加一个空字段，直接进入下一次循环
			return "";
		}else if(buf[0]=='\"'){//读取文本字符段
			return this.readTextField(buf);
		}else{			//读取数字字符段
			return this.readNumberField(buf);
		}
	}

	/**
	 * 读取双引号括起的字符段(保存内容中的双引号)
	 */
	private String readTextField(char[] buf) throws ErrorMessage{
		StringBuilder sb = new StringBuilder();
		while(this.readNext(buf)>0){
			if(buf[0]=='\r'){//在遇到结束双引号之前遇到结束符(不判断逗号,在引号内的逗号可以接受)
				throw new ErrorMessage("Lose concluding quotation marks at line "+String.valueOf(rows+1)+",column "+String.valueOf(this.cols)+".");
			}else if(buf[0]!='\"'){//是一个有效字符，保存
				sb.append(buf[0]);
			}else{//遇到双引号,判断后面是否为结束符
				this.readNext(buf);
				if(buf[0]==','||buf[0]=='\r'){//后面是结束符,退出
					break;
				}else{//后面不是结束符,保存该双引号，继续
					sb.append('\"');
					sb.append(buf[0]);
				}
			}
		}
		return sb.toString();
	}

	/**
	 * 读取非引号括起的数字字符段
	 */
	private String readNumberField(char[] buf) throws ErrorMessage{
		StringBuilder sb = new StringBuilder();
		boolean flg=true;
		sb.append(buf[0]);
		while(this.readNext(buf)>0){
			if(buf[0]==','||buf[0]=='\r'){//遇到结束符
				break;
			}else{
				if(rows==1 && flg==true)
					if(buf[0]=='\r'||buf[0]=='\n'){
						flg=false;
					}
				sb.append(buf[0]);
			}
		}
		return sb.toString();
	}

	/**
	 * 读下一个字节,如果读取失败，置文件结束标志
	 */
	private int readNext(char[] buf) throws ErrorMessage{
		int i=this.read(buf);
		if(i<0 ){
			this.eof=true;
		}
		return i;
	}


	/**
	 * 返回该文件是否已经读取到结尾
	 * @return
	 */
	public boolean eof(){
		return this.eof;
	}

}
